const Plan = function() {
  this.bool = 100;
  this.acc = 1;
  this.def = 1;
};

const plan = new Plan();
plan.bool = 100;
plan.acc = 500;
plan.def = 500;

// Object.create来clone一个一模一样的对象
const clonePlan = Object.create(plan);
console.log(clonePlan, ` clonePlan `);

// 不支持Object.create方法的浏览器可以使用ES5的方式

Object.create =
  Object.create ||
  function(obj) {
    const F = function() {};
    F.prototype = obj;
    return new F();
  };

// 理解new运算的过程
function Person(name) {
  this.name = name;
}

Person.prototype.getName = function() {
  return this.name;
};
// 对象工厂函数
var objectFactory = function() {
  var obj = new Object(); // 从Object.prototype上clone一个空的对象
  Constructor = [].shift.call(arguments); // 获取外部传入的构造器， 此例中指Person
  obj.__proto__ = Constructor.prototype; // 指向正确的原型
  // 修改obj的指向
  var ret = Constructor.apply(obj, arguments); //借用外部传入的构造器给obj设置属性
  return typeof ret === 'object' ? ret : obj; // 确保构造器总会发挥一个对象
};
// shift数组的方法，删除并返回第一个元素
// unshift 数组的开头添加一个元素或更多元素，并返回新的长度
// 对象存在一个隐藏__proto__属性, 而对象的构造器有原型prototype即{Constructor}.prototype
var a = objectFactory(Person, 'seven');

// __proto__就是对象跟‘对象构造器的原型’联系的纽带
/**
 * 对象通过__proto__来记住它的构造器的原型
 * 所以上面的objectFactory函数模拟new的过程，需要手动给obj对象设置正确的__proto__的指向
 */

// new函数调用构造器，构造器返回的一个对象的情况下，返回的this指的返回的对象
var MyClass = function() {
  this.name = 'seven';
  return {
    name: 'amy',
  };
};

const obj = new MyClass();
console.log(obj, ` obj `);

// call和apply改变this的指向

const obj1 = {
  name: 'seven',
  getName: function() {
    return this.name; // 此时的this指的是obj1
  },
};

const obj2 = {
  name: 'six',
};
console.log(obj1.getName(), ` obj1.getName() `); // 'seven'
obj1.getName.apply(obj2);

// 模拟Function.prototype.bind

Function.prototype.bind = function(context) {
  var self = this;
  return function() {
    return self.apply(context, arguments);
  };
};

var obj = {
  name: 'seven',
};

var func = function() {
  alert(this.name);
}.bind(obj);

func();
// 复杂一点的bind,带传参数的

Function.prototype.bind = function() {
  const self = this;
  context = [].shift.call(arguments); // 此时arguments是包含[obj, 1,3]的类数组，shift方法执行之后改变了原数组，将obj对象截取出来了，此时的原数组就只剩[1,3,]
  args = [].slice.call(arguments); // 所以这里的args返回的就是数组[1,3],[].slice.call()方法是将类数组转换成真正的数组
  return function() {
    // 这里的返回的方法其实就是执行mc()函数，所以这里存在的arguments是[4，5]
    return self.apply(context, [].concat.call(args, [].slice.call(arguments))); // args: [1,3], [].slice.call(arguments)这里的返回的就是数组[4,5]
  };
};
var mc = function(a, b, c, d) {
  alert(this.name);
  alert(d);
}.bind(obj, 1, 3);

mc(4, 5);

// 借用其他对象的方法

var A = function() {
  this.nick = nick;
};

var B = function() {
  A.apply(this, arguments);
};

B.prototype.getName = function() {
  return this.name;
};

var newB = new B('seven');
console.log(newB.getName(), ` newB.getName() `); // seven

// --------------------------------------------------
// 使用cache用来缓存，提升性能
var func = (function() {
  var cache = {};
  return function() {
    const label = Array.join.call(arguments);
    if (cache[label]) {
      return cache[label];
    }
    var a = 1;
    for (let index = 0; index < arguments.length; index++) {
      a = a * arguments[index];
    }
    return (cache[label] = a);
  };
})();
// 封装计算函数
var func = (function() {
  var cache = {};
  var calculate = function() {
    var a = 1;
    for (let index = 0; index < arguments.length; index++) {
      a = a * arguments[index];
    }
    return a;
  };
  return function() {
    const label = Array.join.call(arguments);
    if (cache[label]) {
      return cache[label];
    }
    return (cache[label] = calculate.apply(null, arguments));
  };
})();

// img对象用力用来上报数据
var report = (function() {
  var imgs = [];
  return function() {
    var img = new Image(src);
    imgs.push(img);
    img.src = src;
  };
})();

// 闭包实现的功能

var add = function() {
  var a = 1;
  return {
    call: function() {
      a++;
    },
  };
};

var extend = add();
extent.call(); // 1
extent.call(); // 2
extent.call(); // 3
extent.call(); // 4
// 换成对象的写法

var obj = {
  a: 1,
  call: function() {
    this.a++;
  },
};

// 回调函数

/**
 * Ajax异步请求的应用中，回调函数的使用非常频繁。当我们想再ajax请求返回之后，做一些事情，
 * 但又不知道请求返回的确切的时间时，最常见的方案就是把callback函数当作参数传入发起ajax,
 * 请求方法中，待请求完成后执行callback函数
 */

var getUserInfo = function(userId, callback) {
  $.ajax('http://xxx.com/getUserInfo?' + userId, function(data) {
    if (typeof callback === 'function') {
      callback(data);
    }
  });
};

getUserInfo(123, function(ada) {
  console.log(ada);
})[
  // 使用回调完成的ajax异步请求，ES6中使用Promise实现的

  // Array.prototype.sort()方法传入的是一个函数
  ({ age: 1 }, { age: 2 }, { age: 3 })
].sort(function(a, b) {
  return a - b;
});

// 函数作为返回值来执行
var isArray = function() {
  return Object.prototype.toString.call(type) === `[object Array]`;
};

var isType = function(type) {
  return function() {
    return Object.prototype.toString.call(type) === `[object ${type}]`;
  };
};
var bool = isType('Array');

// 函数既作为参数，又作为函数返回
var sigle = function(fn) {
  var ret;
  return function() {
    return ret || (ret = fn.apply(this, arguments)); // 这个地方必须使用（）包裹
  };
};

// 单例模式

var Singleton = function() {
  this.nickame = nickame;
  this.instance = null;
};
Singleton.prototype.getName = function() {
  console.log(this.nickame);
};
Singleton.getInstance = function(nickame) {
  if (!this.instance) {
    this.instance = new Singleton(nickame);
  }
  return this.instance;
};

var a = Singleton.getInstance('sidi');
var b = Singleton.getInstance('moon');

// Singleton.getInstance是用来获取Singleton类的唯一对象

// 单例模式的核心时确保只有一个实例，并提供全局访问

// 微任务： [async1,async2, promise1, end, promise2]
// 宏任务: [setTimeout,]

// 同步代码： start,
// [start,async1,async2, promise1, end, undefind, setTimeout]
